Moving To Windows Part 1
Well we've covered the Win32 essentials in our first tute, now let's get into something a little more useful. We'll be adding a GDI 'frame buffer' to our application and draw something really fancy like a pixel! Anyway let's get to it.
GDI
Let's start and create a 'Virtual Frame Buffer', this will be our 'Back Buffer' and is where we do all our drawing. It does not get displayed on the window, that's the 'Front buffer', so we can modify it without the user knowing. If the user did see it they will see when individual pixels get written instead of seeing the final product, a completed frame. The 'Front Buffer' on the other hand is what the user can see. When we've finished writing to the Back Buffer we 'Flip' the Back Buffer to the Front Buffer, this is known as 'Page Flipping' and allows a smooth transition between the currently displayed image and the image we've just created.
For GDI our Front Buffer is the Win32 Window created in Tute0, and the Back Buffer we'll create by simply allocating a 320x240x32bpp piece of memory and call it our VPage (Virtual Page). We'll use the name Virtual Page so we don't get confused with implementation specific details like a DirectDraw back buffer.
Creating the back buffer is just a simple malloc.
// allocate back buffer
g_VPage = (unsigned short *)malloc( 320*240*4 );
So we use this just like a linear frame buffer, no banking, no weird stride, no dogey pixel format, just a nice flat 8888 (BGRA) frame buffer, simple. Personally I prefer ARGB but Win98 and WinNT DIBs only support BGRA, we'll deal with color space conversions in a different tute. Now before you jump up and down and shout '32bpp MY ARSE! Use 16bpp!', 32bpp is much nicer to work with and this is meant to be a simple tutorial with less emphasis on speed and performance and more on ease of use.
The heart of our GDI VPage will be the StretchDIBits() function. We create a DIB (Device Independent Bitmap) with its memory as our VPage, then let Win32 stretch/squash/mangle/whatever this image into the Win32 window we've created. This creates a very flexible, safe, and easy to debug frame buffer that will always work, i.e. doesn't require other packages like DirectX to be installed to work.
Here's the setup code:
// create a 32bit color DIB
memset( bih, 0, sizeof(bih) );
bih.biSize=sizeof(bih);
bih.biWidth=320;
bih.biHeight=-240; // NOTE: -240. this is because
bih.biPlanes=1; // DIBs are upside down
bih.biBitCount=32;
bih.biCompression=BI_BITFIELDS;
bih.biSizeImage=0;
bih.biXPelsPerMeter=0;
bih.biYPelsPerMeter=0;
bih.biClrUsed=0;
bih.biClrImportant=0;
// setup the pixel format our DIB is in
// 32bit color
((unsigned long*)bi.bmiColors)[0]=0x00ff0000;
((unsigned long*)bi.bmiColors)[1]=0x0000ff00;
((unsigned long*)bi.bmiColors)[2]=0x000000ff;
One gotcha you should look out for is Win32 GDI color space conversions, they are extremely slow, so you should always use the same bpp as the Win32 Desktop, i.e. use a 32bpp DIB if the Win32 desktop is set to 32bpp.
Now to display aka flip our Virtual Page we first get a Win32 Device Context. This is used for performing GDI 'graphics' operations on the window, e.g. Line, Circle, etc. and is usually quite slow. There are an extremely limited number of DCs (Device Contexts) so always make sure you release them when you're finished. It's also good programming practice to check if we got a DC, there will be cases where this fails, as unusual as they are it's best to be sure.
// get the windows Device Context
hDC = GetDC( g_hWnd );
// if it's available
if (hDC != NULL)
{
.
.
.
// now release the Device Context.
ReleaseDC( g_hWnd, hDC );
}
Next we get the current dimensions of our window and perform the blt with StreachDIBits(), this function will magnify/reduce/sheer/scale whatever our DIB to the area described. You can find out more in the Win32 help files. StreachDIBits() can do other things besides a plain copy, like color keying, but we want to maximize its performance and thus do the simplest thing possible.
// Get the window dimensions
GetClientRect(g_hWnd, &client);
// Stretch it
StreachDIBits( hDC,
0, // Destination top left hand corner X Pos
0, // Destination top left hand corner Y Pos
client.right, // Destinations Width
client.bottom, // Destinations Height
0, // Source top left hand corner's X Pos
0, // Source top left hand corner's Y Pos
320, // Sources Width
240, // Sources Height
g_VPage, // Source's data
&bi, // Bitmap Info
DIB_RGB_COLORS, SRCCOPY); // operations
Well that's basically all there is to a simple GDI frame buffer, now let's put it into our project. We'll create a file called surprisingly GDI.cpp, with functions:
GDI_Init();
GDI_Kill();
GDI_Flip();
GDI_GetVPage();
Where Init/Kill will handle the DIB creation/destroying, GDI_Flip() will copy the contents of the virtual page to the window, and GDI_GetVPage() will return a pointer to the top left-hand corner of the linear virtual page.
**Snip**
/*************************************************************************
*
* Title: GDI.cpp
* Desc: Provides a GDI based virtual frame buffer interface
*
* Note: 1) This is only 32bpp
*
*************************************************************************/
#include <Windows.h>
#include <memory.h>
#include "GDI.h"
#include "Winmain.h"
/*************************************************************************
// External Data
/************************************************************************/
/*************************************************************************
// Global Data
/************************************************************************/
/*************************************************************************
// Module Data
/************************************************************************/
static HWND m_hWnd;
// our virtual page
static unsigned int *m_VPage = NULL;
static char m_bibuf[ sizeof(BITMAPINFOHEADER) + 12 ];
static BITMAPINFO &m_bi = *(BITMAPINFO*)&m_bibuf;
static BITMAPINFOHEADER &m_bih = m_bi.bmiHeader;
/*************************************************************************
// Functions
/************************************************************************/
/*************************************************************************
*
* Function: GDI_Init()
*
* Desc: Sets up our DIB and Virtual page
*
* Notes:
*
*************************************************************************/
int GDI_Init( void )
{
// create a 32bit color DIB
memset( &m_bih, 0, sizeof(m_bih) );
m_bih.biSize = sizeof(m_bih);
m_bih.biWidth = 320;
m_bih.biHeight = -240; // NOTE: -240. this is because
m_bih.biPlanes = 1; // DIBs are upside down
m_bih.biBitCount = 32;
m_bih.biCompression = BI_BITFIELDS;
m_bih.biSizeImage = 0;
m_bih.biXPelsPerMeter = 0;
m_bih.biYPelsPerMeter = 0;
m_bih.biClrUsed = 0;
m_bih.biClrImportant = 0;
// setup the format our DIB is in
// 32bit color
((unsigned long*)m_bi.bmiColors)[0]=0x00ff0000;
((unsigned long*)m_bi.bmiColors)[1]=0x0000ff00;
((unsigned long*)m_bi.bmiColors)[2]=0x000000ff;
// allocate vpage
m_VPage = (unsigned int *)malloc( 320 * 240 * 4 );
if (m_VPage == NULL)
{
return 1;
}
return 0;
}
/*************************************************************************
*
* Function: GDI_Kill()
*
* Desc: Releases the Virtual pages memory
*
* Notes:
*
*************************************************************************/
void GDI_Kill( void )
{
// check we allocated it first
if (m_VPage)
{
free( m_VPage );
}
}
/*************************************************************************
*
* Function: GDI_Flip()
*
* Desc: Copyies the virtual page to the window
*
* Notes:
*
*************************************************************************/
void GDI_Flip( void )
{
RECT client;
HDC hDC;
//
// draw it to our window
//
// get the windows Device Context
hDC = GetDC( g_hWnd );
// if it's available
if (hDC != NULL)
{
// Get the window dimensions
GetClientRect(g_hWnd, &client);
// Stretch it
StretchDIBits( hDC,
0, // Destination top left hand
// corner X Position
0, // Destination top left hand
// corner Y Position
client.right, // Destinations width
client.bottom, // Destinations height
0, // Source top left hand
// corner's X Position
0, // Source top left hand
// corner's Y Position
320, // Sources width
240, // Sources height
m_VPage, // Source's data
&m_bi, // Bitmap Info
DIB_RGB_COLORS, // operations
SRCCOPY);
// now release the Device Context.
ReleaseDC( g_hWnd, hDC );
}
}
/*************************************************************************
*
* Function: GDI_GetVPage()
*
* Desc: Gets the virtual page's top left hand corners memory
* address
*
* Notes:
*
*************************************************************************/
unsigned int *GDI_GetVPage( void )
{
return m_VPage;
}
Conclusion
Well that's about it for GDI, there are problems with this, mainly the Desktop's bpp and this stupid BGRA format, which will rectify in the next tute. Until then if you have any comments/queries/problems/bugs don't hesitate to contact me.
Bye.